﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics.Contracts;
using System.Globalization;

namespace TemplateEngine
{
    internal static class TemplateConverter
    {
        private const String WRAPPER_RESULT = "__result";
        private const String PREWRAPPER_RESOURCE = "TemplateEngine.resources.templatePreWrapper.js";
        private const String POSTWRAPPER_RESOURCE = "TemplateEngine.resources.templatePostWrapper.js";
        private static String _prewrapper;
        private static String PreWrapper
        {
            get
            {
                if (_prewrapper == null)
                {
                    _prewrapper = Common.ResourceUtil.GetResourceAsText(PREWRAPPER_RESOURCE);
                }
                return _prewrapper;
            }
            set
            {
                _prewrapper = value;
            }
        }
        private static String _postwrapper;
        private static String PostWrapper
        {
            get
            {
                if (_postwrapper == null)
                {
                    _postwrapper = Common.ResourceUtil.GetResourceAsText(POSTWRAPPER_RESOURCE);
                }
                return _postwrapper;
            }
            set
            {
                _postwrapper = value;
            }
        }

        public static ITemplate loadTemplate(FileInfo file)
        {
            Contract.Requires(file != null);
            Contract.Requires(file.Exists);
            using (StreamReader sr = file.OpenText())
            {
                return loadTemplate(sr);
            }
        }

        public static ITemplate loadTemplate(TextReader tr)
        {
            Contract.Requires(tr != null);
            PeekReader pr = new PeekReader(tr);
            StringBuilder sb = new StringBuilder();
            sb.Append(PreWrapper);
            readAnything(sb, pr);
            sb.Append(PostWrapper);
            return new V8Template(sb.ToString(), WRAPPER_RESULT);
        }

        public static ITemplate loadTemplate(String templateText)
        {
            Contract.Requires(templateText != null);
            PeekReader pr = new PeekReader(templateText.ToCharArray());
            StringBuilder sb = new StringBuilder();
            sb.Append(PreWrapper);
            readAnything(sb, pr);
            sb.Append(PostWrapper);
            return new V8Template(sb.ToString(), WRAPPER_RESULT);
        }

        private static readonly char[] DIRECTIVE_START = "$[".ToCharArray();
        private static readonly char[] DIRECTIVE_END = "]".ToCharArray();
        private static readonly char[] LOOKUP_START = "${".ToCharArray();
        private static readonly char[] LOOKUP_END = "}".ToCharArray();
        private static readonly char[] BRACKET_START = "(".ToCharArray();
        private static readonly char[] BRACKET_END = ")".ToCharArray();
        private static readonly char[] SQUARE_BRACKET_START = "[".ToCharArray();
        private static readonly char[] SQUARE_BRACKET_END = "]".ToCharArray();
        private static readonly char[] QUOTE_CHAR = "\"".ToCharArray();
        private static readonly char[] ESCAPE_QUOTE = "\\\"".ToCharArray();


        private static void readAnything(StringBuilder result, PeekReader tr)
        {
            Contract.Requires(result != null);
            Contract.Requires(tr != null);
            StringBuilder text = new StringBuilder();
            bool didCommand = false;
            while (!tr.EOF)
            {
                if (!didCommand)
                {
                    text.Append(tr.ReadChar());
                }
                didCommand = false;
                if (tr.PeekMatch(LOOKUP_START))
                {
                    writeText(result, text);
                    StringBuilder commandSB = new StringBuilder();
                    readLookup(commandSB, tr);
                    String command = commandSB.ToString();
                    if (!isSpecialDirective(command))
                    {
                        result.Append("out(");
                        result.Append(command);
                        result.AppendLine(");");
                    }
                    else
                    {
                        result.AppendLine(replaceSpecialDirectives(command));
                    }
                    didCommand = true;
                }
                else if (tr.PeekMatch(DIRECTIVE_START))
                {
                    writeText(result, text);
                    StringBuilder commandSB = new StringBuilder();
                    readDirective(commandSB, tr);
                    String command = commandSB.ToString();
                    result.AppendLine(replaceSpecialDirectives(command));
                    didCommand = true;
                }
            }
            writeText(result, text);
        }
        private static void writeText(StringBuilder result, StringBuilder text)
        {
            Contract.Requires(result != null);
            Contract.Requires(text != null);
            String t = text.ToString();
            if (t.Trim().Length == 0)
            {
                if (t.Contains(" ")) { t = " "; } else { return; }
            }
            t = t.Replace("\"", "\\\"");
            t = t.Replace("\r", "\\r");
            t = t.Replace("\n", "\\n");

            result.Append("out(\"");
            result.Append(t);
            result.AppendLine("\");");
            text.Length = 0;
        }
        private static void readLookup(StringBuilder result, PeekReader tr)
        {
            Contract.Requires(result != null);
            Contract.Requires(tr != null);
            tr.Skip(LOOKUP_START);
            while (!tr.EOF)
            {
                result.Append(tr.ReadChar());
                if (tr.PeekMatch(BRACKET_START))
                {
                    readBrackets(result, tr);
                }
                else if (tr.PeekMatch(SQUARE_BRACKET_START))
                {
                    readSquareBrackets(result, tr);
                }
                else if (tr.PeekMatch(QUOTE_CHAR))
                {
                    readQuotes(result, tr);
                }
                else if (tr.PeekMatch(LOOKUP_END))
                {
                    tr.Skip(LOOKUP_END);
                    return;
                }
            }
        }
        private static void readDirective(StringBuilder result, PeekReader tr)
        {
            Contract.Requires(result != null);
            Contract.Requires(tr != null);
            tr.Skip(DIRECTIVE_START);
            while (!tr.EOF)
            {
                if (tr.PeekMatch(BRACKET_START))
                {
                    readBrackets(result, tr);
                }
                else if (tr.PeekMatch(SQUARE_BRACKET_START))
                {
                    readSquareBrackets(result, tr);
                }
                else if (tr.PeekMatch(QUOTE_CHAR))
                {
                    readQuotes(result, tr);
                }
                else if (tr.PeekMatch(DIRECTIVE_END))
                {
                    tr.Skip(DIRECTIVE_END);
                    return;
                }
                else
                {
                    result.Append(tr.ReadChar());
                }
            }
        }
        private static void readBrackets(StringBuilder result, PeekReader tr)
        {
            Contract.Requires(result != null);
            Contract.Requires(tr != null);
            result.Append(tr.ReadChar());
            while (!tr.EOF)
            {
                if (tr.PeekMatch(BRACKET_START))
                {
                    readBrackets(result, tr);
                }
                else if (tr.PeekMatch(SQUARE_BRACKET_START))
                {
                    readSquareBrackets(result, tr);
                }
                else if (tr.PeekMatch(QUOTE_CHAR))
                {
                    readQuotes(result, tr);
                }
                else if (tr.PeekMatch(BRACKET_END))
                {
                    result.Append(tr.ReadChar());
                    return;
                }
                else
                {
                    result.Append(tr.ReadChar());
                }
            }
        }
        private static void readSquareBrackets(StringBuilder result, PeekReader tr)
        {
            Contract.Requires(result != null);
            Contract.Requires(tr != null);
            result.Append(tr.ReadChar());
            while (!tr.EOF)
            {
                if (tr.PeekMatch(BRACKET_START))
                {
                    readBrackets(result, tr);
                }
                else if (tr.PeekMatch(SQUARE_BRACKET_START))
                {
                    readSquareBrackets(result, tr);
                }
                else if (tr.PeekMatch(QUOTE_CHAR))
                {
                    readQuotes(result, tr);
                }
                else if (tr.PeekMatch(SQUARE_BRACKET_END))
                {
                    result.Append(tr.ReadChar());
                    return;
                }
                else
                {
                    result.Append(tr.ReadChar());
                }
            }
        }
        private static void readQuotes(StringBuilder result, PeekReader tr)
        {
            Contract.Requires(result != null);
            Contract.Requires(tr != null);
            result.Append(tr.ReadChar());
            while (!tr.EOF)
            {
                if (tr.PeekMatch(ESCAPE_QUOTE))
                {
                    result.Append(tr.ReadChars(ESCAPE_QUOTE.Length));
                }
                else if (tr.PeekMatch(QUOTE_CHAR))
                {
                    result.Append(tr.ReadChar());
                    return;
                }
                else
                {
                    result.Append(tr.ReadChar());
                }
            }
        }
        private static readonly Regex beginLoop = new Regex("^ foreach (.+) in (.+) $".Replace(" ", "\\s*"), RegexOptions.IgnoreCase & RegexOptions.IgnorePatternWhitespace);
        private static readonly Regex endLoop = new Regex("^ / foreach $".Replace(" ", "\\s*"), RegexOptions.IgnoreCase & RegexOptions.IgnorePatternWhitespace);
        private static String replaceSpecialDirectives(String rawDirective)
        {
            Contract.Requires(rawDirective != null);
            Match m = beginLoop.Match(rawDirective);
            if (m.Success)
            {
                return String.Format(CultureInfo.InvariantCulture, "foreach({1},function({0}){{", m.Groups[1].Value.Trim(), m.Groups[2].Value.Trim());
            }
            else if ((m = endLoop.Match(rawDirective)).Success)
            {
                return "});";
            }
            else
            {
                return rawDirective;
            }
        }
        private static bool isSpecialDirective(String rawDirective)
        {
            Contract.Requires(rawDirective != null);
            return beginLoop.IsMatch(rawDirective) || endLoop.IsMatch(rawDirective);
        }
    }
}

